package references

import (
	"bytes"
	"encoding/binary"
	"math"
	"strings"

	"github.com/ethereum/go-ethereum/rlp"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/common"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/instructions/base"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda/heap"
	"hundsun.com/hsl/hschain/store/extend/contract"
)

// Create new object
type New struct {
	base.Index16Instruction
	class        *heap.Class
	fieldDataMap map[string][]byte
}

func (instr *New) loadDataFromStorage(rt *heap.Runtime, class *heap.Class) *heap.Object {
	//new object
	ref := class.NewObj()
	for _, field := range class.Fields {
		findName := field.Class.Name + "." + field.Name
		valBytes, ok := instr.fieldDataMap[findName]
		switch field.Descriptor {
		case "Z", "B", "C", "S", "I", "Ljava/lang/Boolean;", "Ljava/lang/Byte;",
			"Ljava/lang/Character;", "Ljava/lang/Short;", "Ljava/lang/Integer;":
			//val := field.GetValue()
			var x int32
			if ok {
				if field.Descriptor == "Z" || field.Descriptor == "B" || field.Descriptor == "Ljava/lang/Boolean;" ||
					field.Descriptor == "Ljava/lang/Byte;" {
					x = int32(valBytes[0])
				} else if field.Descriptor == "C" || field.Descriptor == "S" ||
					field.Descriptor == "Ljava/lang/Character;" || field.Descriptor == "Ljava/lang/Short;" {
					x = int32(uint16(valBytes[1])<<8 + uint16(valBytes[0]))
				} else {
					bytesBuffer := bytes.NewBuffer(valBytes)
					binary.Read(bytesBuffer, binary.BigEndian, &x)
				}
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					fieldObj.Fields.([]heap.Slot)[0].Val = int64(x)
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewIntSlot(x))
				}
			} else {
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewIntSlot(0))
				}
			}
		case "J", "Ljava/lang/Long;":
			if ok {
				bytesBuffer := bytes.NewBuffer(valBytes)
				var x int64
				binary.Read(bytesBuffer, binary.BigEndian, &x)
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					fieldObj.Fields.([]heap.Slot)[0].Val = x
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewLongSlot(x))
				}
			} else {
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewLongSlot(0))
				}
			}
		case "F", "Ljava/lang/Float;":
			if ok {
				bits := binary.LittleEndian.Uint32(valBytes)
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					fieldObj.Fields.([]heap.Slot)[0].Val = int64(bits)
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.Slot{Val: int64(bits)})
				}
			} else {
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.Slot{Val: int64(0)})
				}
			}
		case "D", "Ljava/lang/Double;":
			if ok {
				bits := binary.LittleEndian.Uint64(valBytes)
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					fieldObj.Fields.([]heap.Slot)[0].Val = int64(bits)
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.Slot{Val: int64(bits)})
				}
			} else {
				if field.Descriptor[0] == 'L' {
					fieldClass := rt.BootLoader().LoadClass(field.Descriptor[1 : len(field.Descriptor)-1])
					fieldObj := fieldClass.NewObj()
					ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldObj))
				} else {
					ref.SetFieldValue(field.Name, field.Descriptor, heap.Slot{Val: int64(0)})
				}
			}
		case "Ljava/lang/String;":
			//basic type convert to bytes and write to storage
			if ok {
				val := string(valBytes)
				ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(rt.JSFromGoStrNoIntern(val)))
			} else {
				ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(rt.JSFromGoStr("")))
			}
		case "[B", "[Z", "[V", "[Ljava/lang/Byte;", "[Ljava/lang/Boolean;", "[Ljava/lang/Void;":
			class := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			arrRef := heap.NewArray(class, uint(len(valBytes)))
			for i := 0; i < len(valBytes); i++ {
				if len(field.Descriptor) == 2 {
					arrRef.GetBytes()[i] = int8(valBytes[i])
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(valBytes[i])
					arrRef.GetRefs()[i] = componentObj
				}
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[C", "[Ljava/lang/Character;":
			class := instr.class.GetBootLoader().LoadClass("[C")
			arrRef := heap.NewArray(class, uint(len(valBytes)/2))
			j := 0
			for i := 0; i+2 <= len(valBytes); i += 2 {
				bytesBuffer := bytes.NewBuffer([]byte{valBytes[i], valBytes[i+1]})
				var x int32
				binary.Read(bytesBuffer, binary.BigEndian, &x)
				if field.Descriptor == "[C" {
					arrRef.GetChars()[j] = uint16(x)
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(x)
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[S", "[Ljava/lang/Short;":
			class := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			arrRef := heap.NewArray(class, uint(len(valBytes)/2))
			j := 0
			for i := 0; i+2 <= len(valBytes); i += 2 {
				bytesBuffer := bytes.NewBuffer([]byte{valBytes[i], valBytes[i+1]})
				var x int32
				binary.Read(bytesBuffer, binary.BigEndian, &x)
				if field.Descriptor == "[S" {
					arrRef.GetShorts()[j] = int16(x)
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(x)
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[I", "[Ljava/lang/Integer;":
			class := instr.class.GetBootLoader().LoadClass("[I")
			arrRef := heap.NewArray(class, uint(len(valBytes)/4))
			j := 0
			for i := 0; i+4 <= len(valBytes); i += 4 {
				bytesBuffer := bytes.NewBuffer(valBytes[i : i+4])
				var x int32
				binary.Read(bytesBuffer, binary.BigEndian, &x)
				if field.Descriptor == "[I" {
					arrRef.GetInts()[j] = x
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(x)
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[J", "[Ljava/lang/Long;":
			class := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			arrRef := heap.NewArray(class, uint(len(valBytes)/8))
			j := 0
			for i := 0; i+8 <= len(valBytes); i += 8 {
				bytesBuffer := bytes.NewBuffer(valBytes[i : i+8])
				var l int64
				binary.Read(bytesBuffer, binary.BigEndian, &l)
				if field.Descriptor == "[L" {
					arrRef.GetLongs()[j] = l
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = l
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[F", "[Ljava/lang/Float;":
			class := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			arrRef := heap.NewArray(class, uint(len(valBytes)/4))
			j := 0
			for i := 0; i+4 <= len(valBytes); i += 4 {
				var f float32
				bits := binary.LittleEndian.Uint32(valBytes[i : i+4])
				if field.Descriptor == "[F" {
					f = math.Float32frombits(bits)
					arrRef.GetFloats()[j] = f
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(bits)
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[D", "[Ljava/lang/Double;":
			class := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			arrRef := heap.NewArray(class, uint(len(valBytes)/8))
			j := 0
			for i := 0; i+8 <= len(valBytes); i += 8 {
				var d float64
				bits := binary.LittleEndian.Uint64(valBytes[i : i+8])
				d = math.Float64frombits(bits)
				if field.Descriptor == "[D" {
					arrRef.GetDoubles()[j] = d
				} else {
					componentClass := class.GetComponentClass()
					componentObj := componentClass.NewObj()
					componentObj.Fields.([]heap.Slot)[0].Val = int64(bits)
					arrRef.GetRefs()[j] = componentObj
				}
				j++
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		case "[Ljava/lang/String;":
			arrClass := instr.class.GetBootLoader().LoadClass(field.Descriptor)
			var allStrings [][]byte
			arrLen := 0

			if valBytes != nil && len(valBytes) > 0 {
				err := rlp.DecodeBytes(valBytes, &allStrings)
				if err != nil {
					panic("loadDataFromStorage decode strings failed " + err.Error())
				}
				arrLen = len(allStrings)
			}
			arrRef := heap.NewArray(arrClass, uint(arrLen))
			for i := 0; i < arrLen; i++ {
				var str string
				rlp.DecodeBytes(allStrings[i], &str)
				arrRef.GetRefs()[i] = rt.JSFromGoStrNoIntern(str)
			}
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(arrRef))
		default:
			//暂时不支持比较复杂的引用数组
			if strings.HasPrefix(field.Descriptor, "[") {
				panic("Not support reference array")
			}
			//引用类型
			fieldRef := instr.loadDataFromStorage(rt, field.Type())
			ref.SetFieldValue(field.Name, field.Descriptor, heap.NewRefSlot(fieldRef))
		}
	}
	return ref
}
func (instr *New) getAllStorageFields(class *heap.Class) []string {
	var allFields []string
	for _, field := range class.Fields {
		findName := field.Class.Name + "." + field.Name
		filedClassName := field.Descriptor
		if strings.HasPrefix(filedClassName, "L") {
			filedClassName = filedClassName[1 : len(filedClassName)-1]
		}

		isHaveFields := true
		//基本类型，String和数组类型不需要处理fields
		if heap.IsPrimitiveType(filedClassName) || filedClassName == "java/lang/String" || filedClassName[0] == '[' {
			isHaveFields = false
		}
		if !isHaveFields {
			allFields = append(allFields, findName)
		} else {
			fieldType := field.Type()
			fieldAllFields := instr.getAllStorageFields(fieldType)
			allFields = append(allFields, fieldAllFields...)
		}
	}
	return allFields
}
func (instr *New) Execute(frame *rtda.Frame) {
	if instr.class == nil {
		cp := frame.GetConstantPool()
		kClass := cp.GetConstantClass(instr.Index)
		instr.class = kClass.GetClass()
	}

	// init class
	if instr.class.InitializationNotStarted() {
		frame.RevertNextPC() // undo new
		frame.Thread.InitClass(instr.class)
		return
	}

	ref := instr.class.NewObj()
	mainClassName := frame.Thread.MainClass
	callClassName := mainClassName[:len(mainClassName)-4]
	ctx := frame.Thread.ExecCtx
	if instr.class.Name == callClassName {
		if instr.fieldDataMap == nil {
			allStorageFields := instr.getAllStorageFields(instr.class)
			contractName := frame.Thread.ContractName
			mapFindKeys := make(map[string][]byte)
			for _, f := range allStorageFields {
				fieldValue, _ := ctx.Get(contract.GenContractStatusKey(f, contractName, common.ContractStatusTypeField))
				mapFindKeys[f] = fieldValue
			}
			instr.fieldDataMap = mapFindKeys
		}
		//fmt.Println("New instruction field data map ",instr.fieldDataMap)
		if len(instr.fieldDataMap) == 0 {
			ref = instr.class.NewObj()
		} else {
			ref = instr.loadDataFromStorage(frame.Thread.Runtime, instr.class)
		}
	}
	frame.PushRef(ref)
}
